"""
Copyright 2012 Wieser Dietmar, Margit Mutschlechner.

This file is part of Multimedia Social Networking Platform.

Multimedia Social Networking Platform is free software: 
you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

Multimedia Social Networking Platform is distributed
in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with Multimedia Social Networking Platform.  
If not, see <http://www.gnu.org/licenses/>.

"""

from msnp.logger import Logger
from msnp.model import MediaModel
from msnp.persistence import PersistenceController
from msnp.service.synch import SynchChange, TagSynch
from msnp.gui import MySignal

class AmarokSynchChangesWriter(SynchChange.SynchChangesWriter):
    """Class for synchronizing media content from the Amarok database into the media database.
    
    Orders all changes and calls for each the processChange method of the super class.
    
    Parameters:
        __insertChanges: A list of SynchChanges objects containing all happened insertions.
        __updateChanges: A list of SynchChanges objects containing all happened updates.
    """
    
    def __init__(self, insertChanges, updateChanges):
        """The init method."""
        self.__insertChanges = insertChanges
        self.__updateChanges = updateChanges
        SynchChange.SynchChangesWriter.__init__(self)
    
    def performChangeComparison(self):
        """Orders and groups the update and insert changes and processes them."""
        connectedInsertList = self.__orderConnectedChanges(self.__insertChanges)
        connectedUpdateList = self.__orderConnectedChanges(self.__updateChanges)
        self.__processConnectedChangesList(connectedInsertList, connectedUpdateList)
        
    def __orderConnectedChanges(self, changes):
        """Orders and groups changes.
        
        Groups the list of connected changes into lists of lists of SynchChanges objects.
        Two changes are in the same list if their table, row and event are the same.
        
        Args:
            changes: A list of SynchChanges objects containing the changes to be ordered.
            
        Returns:
            An ordered list of connected changes (list of SynchChanges objects) is returned.
        """
        #a list of a list of connected changes ;)
        connectedChangesList = []
        #a list of connected changes
        connectedChanges = []
        prevChange = None
        if changes != None:
            for change in changes:
                if prevChange != None and prevChange.isConnected(change):
                    connectedChanges.append(change)
                else:
                    #process connected changes
                    if len(connectedChanges) > 0:
                        connectedChangesList.append(connectedChanges)
                    connectedChanges = []
                    prevChange = change
                    connectedChanges.append(change)
        if len(connectedChanges) > 0:
            connectedChangesList.append(connectedChanges)
        if len(connectedChangesList) == 0:
            connectedChangesList = None
        return connectedChangesList
                
    def __processConnectedChangesList(self, connectedInsertList, connectedUpdateList):
        """Processes the list of lists of SynchChanges objects.
        
        Processes the two lists by calling the __processConnectedChanges method
        for each list of connected changes, first for the inserts, then for the updates.
        
        Args:
            connectedInsertList: A list of connected changes (list of SynchChanges objects)
                containing the grouped insert changes.
            connectedUpdateList: A list of connected changes (list of SynchChanges objects)
                containing the grouped update changes.
        """
        #first run -> process only inserts
        if connectedInsertList != None:
            for connectedChanges in connectedInsertList:
                self.__processConnectedChanges(connectedChanges, MediaModel.SynchChangeEvent.INSERT)
        #second run -> process only updates
        if connectedUpdateList != None:
            for connectedChanges in connectedUpdateList:
                self.__processConnectedChanges(connectedChanges, MediaModel.SynchChangeEvent.UPDATE)
        
    def __processConnectedChanges(self, connectedChanges, eventToProcess):
        """Processes a list of connected changes.
        
        Retrieves the audio of the Amarok database that corresponds to the table and row id
        of the first item of the connectedChanges list (could take any item,
        all items have the same table and row id).
        Calls the processChange method of the super class to process the change.
        
        Args:
            connectedChanges: A list of connected changes (list of SynchChanges objects).
            eventToProcess: A String, either UPDATE or INSERT.
        """
        if connectedChanges != None and len(connectedChanges) > 0:
            change = connectedChanges[0]
            rowId = change.getRowId()
            tableName = change.getTablename()
            if change.getEvent() == eventToProcess:
                trackId = PersistenceController.selectPlayerTrackId(tableName, rowId)
                if trackId != None:
                    amarokAudios = PersistenceController.selectPlayerAudiosFromPlayerDB(MediaModel.Player.AMAROK().getName(), trackId)
                    if amarokAudios != None and len(amarokAudios) > 0:
                        #there is only one result, but nevertheless implemented as if there would be more results
                        for amarokAudio in amarokAudios:
                            self.processChange(amarokAudio, table=tableName)
                    else:
                        Logger.info("no audios found in amarok for trackid: " + str(trackId))
                else:
                    Logger.info("trackid None in amarok->__processConnectedChanges")
        else:
            Logger.info("connectedChanges None or empty in processConnectedChanges - Should not happen!!!")
            
class AmarokSynchChangesReader(SynchChange.SynchChangesReader):
    """Class for synchronizing media content from the media database towards the Amarok database.
    
    Retrieves the audios changed since the last synchronization towards the Amarok player
    and processes them.
    """

    def __init__(self):
        """The init method."""
        SynchChange.SynchChangesReader.__init__(self)
        
    def processChanges(self):
        """Retrieves all changed audios and inserts them into the Amarok database.
        
        Calls the getAudiosToSynch method of the super class to retrieve all audios of the media database
        that were changed since the last synchronization.
        Calls for every such item the __processAmarokTrackIds method that processes it
        and inserts it into the Amarok database by editing the underlying file's metadata.
        
        Returns:
            True is returned if there were changed items to update, False otherwise.
        """
        audiosToSynch = self.getAudiosToSynch()
        if audiosToSynch != None and len(audiosToSynch) > 0:
            MySignal.emitOpenProgressBarSignal(len(audiosToSynch), False)
            progressCount = 1
            for mdbAudio in audiosToSynch:
                MySignal.emitUpdateProgressBarSignal(progressCount)
                title = mdbAudio.getMedia().getTitle()
                artistName = mdbAudio.getArtist().getName()
                collectionName = None
                if mdbAudio.getMedia().getCollection() != None:
                    collectionName = mdbAudio.getMedia().getCollection().getName()
                amarokIds = PersistenceController.selectTrackIdsForTitleArtistAndCollection(MediaModel.Player.AMAROK().getName(), title, artistName, collectionName)
                if amarokIds != None:
                    self.__processAmarokTrackIds(amarokIds, mdbAudio)
                progressCount += 1
            return True
        return False
    
    def __processAmarokTrackIds(self, amarokIds, mdbAudio):
        """Updates a certain entry of the Amarok database.
        
        The entries of the Amarok database, that correspond to the entry of the media database,
        are updated by editing the metadata of the underlying files.
        
        Args:
            amarokIds: A list of integers containing the ids of all entries in the Amarok database
                that correspond to the mdbAudio object of the media database.
            mdbAudio: The Audio object where the corresponding entry in the Amarok database has to be updated. 
        """
        for trackId in amarokIds:
            fileUrl = PersistenceController.selectFileUrlForTrackId(MediaModel.Player.AMAROK().getName(), trackId)
            if fileUrl != None:
                #synch id3 tags of file
                TagSynch.writeTags(fileUrl, mdbAudio)
                #update lyrics
                lyrics = mdbAudio.getLyrics()
                if lyrics != None and len(lyrics) > 0:
                    PersistenceController.insertOrUpdateLyricsForUrl(MediaModel.Player.AMAROK().getName(), lyrics, fileUrl)
